package zabbix

import (
	"context"
	"testing"
	"time"

	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetFullVersion(t *testing.T) {
	zabbixClient, err := MockZabbixClient(BasicDatasourceInfo, `{"result":"5.0.12"}`, 200)
	require.NoError(t, err)

	version, err := zabbixClient.GetFullVersion(context.Background())
	assert.NoError(t, err)
	assert.Equal(t, "5.0.12", version)
}

func TestGetFullVersionInvalidPayload(t *testing.T) {
	zabbixClient, err := MockZabbixClient(BasicDatasourceInfo, `{"result":{"version":"5.0.12"}}`, 200)
	require.NoError(t, err)

	version, err := zabbixClient.GetFullVersion(context.Background())
	assert.Error(t, err)
	assert.Equal(t, "", version)
}

func TestGetVersion(t *testing.T) {
	zabbixClient, err := MockZabbixClient(BasicDatasourceInfo, `{"result":"5.4.3"}`, 200)
	require.NoError(t, err)

	version, err := zabbixClient.GetVersion(context.Background())
	assert.NoError(t, err)
	assert.Equal(t, 54, version)
}

func TestGetVersionReturnsError(t *testing.T) {
	zabbixClient, err := MockZabbixClient(BasicDatasourceInfo, `{"result":"a.b.c"}`, 200)
	require.NoError(t, err)

	version, err := zabbixClient.GetVersion(context.Background())
	assert.Error(t, err)
	assert.Equal(t, 0, version)
}

func TestGetHistory(t *testing.T) {
	var historyCalls []int
	client := NewZabbixClientWithHandler(t, func(payload ApiRequestPayload) string {
		if payload.Method == "history.get" {
			historyCalls = append(historyCalls, int(payload.Params["history"].(float64)))
			return `{"result":[{"itemid":"1","clock":"1","value":"1.2","ns":"0"}]}`
		}
		if payload.Method ==  "apiinfo.version" {
			return `{"result":"6.4.0"}`
		}
		return `{"result":null}`
	})

	items := []*Item{
		{ID: "10", ValueType: 0},
		{ID: "20", ValueType: 3},
	}
	tr := backend.TimeRange{
		From: time.Unix(0, 0),
		To:   time.Unix(10, 0),
	}

	history, err := client.GetHistory(context.Background(), items, tr)
	require.NoError(t, err)
	assert.Len(t, history, 2)
	assert.ElementsMatch(t, []int{0, 3}, historyCalls)
}

func TestGetTrend(t *testing.T) {
	var capturedIDs []string
	client := NewZabbixClientWithHandler(t, func(payload ApiRequestPayload) string {
		if payload.Method == "trend.get" {
			for _, raw := range payload.Params["itemids"].([]interface{}) {
				capturedIDs = append(capturedIDs, raw.(string))
			}
			return `{"result":[{"itemid":"1","clock":"1","value_min":"0","value_avg":"1","value_max":"2"}]}`
		}

		if payload.Method ==  "apiinfo.version" {
			return `{"result":"6.4.0"}`
		}
		return `{"result":null}`
	})

	items := []*Item{{ID: "100"}, {ID: "200"}}
	tr := backend.TimeRange{
		From: time.Unix(0, 0),
		To:   time.Unix(10, 0),
	}

	trend, err := client.GetTrend(context.Background(), items, tr)
	require.NoError(t, err)
	assert.Len(t, trend, 1)
	assert.ElementsMatch(t, []string{"100", "200"}, capturedIDs)
}

func TestGetItems(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"hostgroup.get": `{"result":[{"groupid":"1","name":"Servers"}]}`,
		"host.get":      `{"result":[{"hostid":"10","name":"web01"}]}`,
		"item.get": `{
			"result":[
				{"itemid":"100","name":"CPU usage"},
				{"itemid":"200","name":"Memory usage"}
			]
		}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	items, err := client.GetItems(context.Background(), "Servers", "web01", "", "/CPU/", "num", false)
	require.NoError(t, err)
	if assert.Len(t, items, 1) {
		assert.Equal(t, "100", items[0].ID)
	}
}

func TestGetItemsBefore54(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"hostgroup.get": `{"result":[{"groupid":"1","name":"Servers"}]}`,
		"host.get":      `{"result":[{"hostid":"10","name":"web01"}]}`,
		"application.get": `{
			"result":[
				{"applicationid":"50","name":"Databases"},
				{"applicationid":"60","name":"Apps"}
			]
		}`,
		"item.get": `{
			"result":[
				{"itemid":"500","name":"DB Size"},
				{"itemid":"600","name":"API latency"}
			]
		}`,
		"apiinfo.version": `{"result":"5.0.0"}`,
	})

	items, err := client.GetItemsBefore54(context.Background(), "Servers", "web01", "Databases", "/DB/", "num", false)
	require.NoError(t, err)
	if assert.Len(t, items, 1) {
		assert.Equal(t, "500", items[0].ID)
	}
}

func TestFilterItemsByQuery(t *testing.T) {
	items := []*Item{
		{Name: "CPU usage"},
		{Name: "Memory usage"},
	}

	filtered, err := filterItemsByQuery(items, "/CPU/")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "CPU usage", filtered[0].Name)

	filtered, err = filterItemsByQuery(items, "Memory usage")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "Memory usage", filtered[0].Name)
}

func TestGetApps(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"hostgroup.get": `{"result":[{"groupid":"1","name":"Servers"}]}`,
		"host.get":      `{"result":[{"hostid":"10","name":"web01"}]}`,
		"application.get": `{
			"result":[
				{"applicationid":"50","name":"API"},
				{"applicationid":"60","name":"DB"}
			]
		}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	apps, err := client.GetApps(context.Background(), "Servers", "web01", "/^API/")
	require.NoError(t, err)
	if assert.Len(t, apps, 1) {
		assert.Equal(t, "50", apps[0].ID)
	}
}

func TestFilterAppsByQuery(t *testing.T) {
	apps := []Application{
		{Name: "API"},
		{Name: "DB"},
	}

	filtered, err := filterAppsByQuery(apps, "/^A/")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "API", filtered[0].Name)

	filtered, err = filterAppsByQuery(apps, "DB")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "DB", filtered[0].Name)
}

func TestGetItemTags(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"hostgroup.get": `{"result":[{"groupid":"1","name":"Servers"}]}`,
		"host.get":      `{"result":[{"hostid":"10","name":"web01"}]}`,
		"item.get": `{
			"result":[
				{"itemid":"1","name":"CPU","tags":[{"tag":"Env","value":"prod"},{"tag":"Application","value":"api"}]},
				{"itemid":"2","name":"Mem","tags":[{"tag":"Env","value":"stage"},{"tag":"Env","value":"prod"}]}
			]
		}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	tags, err := client.GetItemTags(context.Background(), "Servers", "web01", "/^Env/")
	require.NoError(t, err)
	assert.ElementsMatch(t, []ItemTag{
		{Tag: "Env", Value: "prod"},
		{Tag: "Env", Value: "stage"},
	}, tags)
}

func TestFilterTags(t *testing.T) {
	tags := []ItemTag{
		{Tag: "Env", Value: "prod"},
		{Tag: "Application", Value: "api"},
	}

	filtered, err := filterTags(tags, "/^Env/")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "Env", filtered[0].Tag)

	filtered, err = filterTags(tags, "Application: api")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "Application", filtered[0].Tag)
}

func TestGetHosts(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"hostgroup.get": `{"result":[{"groupid":"1","name":"Servers"}]}`,
		"host.get": `{
			"result":[
				{"hostid":"10","name":"web01"},
				{"hostid":"20","name":"db01"}
			]
		}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	hosts, err := client.GetHosts(context.Background(), "Servers", "/web/")
	require.NoError(t, err)
	if assert.Len(t, hosts, 1) {
		assert.Equal(t, "web01", hosts[0].Name)
	}
}

func TestFilterHostsByQuery(t *testing.T) {
	hosts := []Host{
		{Name: "web01"},
		{Name: "db01"},
	}

	filtered, err := filterHostsByQuery(hosts, "/^web/")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "web01", filtered[0].Name)

	filtered, err = filterHostsByQuery(hosts, "db01")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "db01", filtered[0].Name)
}

func TestGetGroups(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"hostgroup.get": `{"result":[{"groupid":"1","name":"Servers"},{"groupid":"2","name":"Apps"}]}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	groups, err := client.GetGroups(context.Background(), "/Apps/")
	require.NoError(t, err)
	if assert.Len(t, groups, 1) {
		assert.Equal(t, "Apps", groups[0].Name)
	}
}

func TestFilterGroupsByQuery(t *testing.T) {
	groups := []Group{
		{Name: "Servers"},
		{Name: "Apps"},
	}

	filtered, err := filterGroupsByQuery(groups, "/Apps/")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "Apps", filtered[0].Name)

	filtered, err = filterGroupsByQuery(groups, "Servers")
	require.NoError(t, err)
	assert.Len(t, filtered, 1)
	assert.Equal(t, "Servers", filtered[0].Name)
}

func TestGetAllItemsBuildsParams(t *testing.T) {
	var lastRequest ApiRequestPayload
	client := NewZabbixClientWithHandler(t, func(payload ApiRequestPayload) string {
		if payload.Method == "item.get" {
			lastRequest = payload
			return `{"result":[{"itemid":"1","name":"CPU $1","key_":"system.cpu[user]","value_type":"0","hosts":[{"hostid":"10","name":"web"}]}]}`
		}

		if payload.Method ==  "apiinfo.version" {
			return `{"result":"6.4.0"}`
		}

		return `{"result":null}`
	})

	items, err := client.GetAllItems(
		context.Background(),
		[]string{"10"},
		nil,
		"num",
		false,
		"Env: prod, Application: api",
	)
	require.NoError(t, err)
	if assert.Len(t, items, 1) {
		assert.Equal(t, "CPU user", items[0].Name)
	}

	require.Equal(t, "item.get", lastRequest.Method)
	params := lastRequest.Params
	assert.Equal(t, []interface{}{"10"}, params["hostids"])
	assert.Equal(t, true, params["monitored"])
	filter := params["filter"].(map[string]interface{})
	assert.ElementsMatch(t, []interface{}{float64(0), float64(3)}, filter["value_type"].([]interface{}))
	tags := params["tags"].([]interface{})
	if assert.Len(t, tags, 2) {
		first := tags[0].(map[string]interface{})
		second := tags[1].(map[string]interface{})
		assert.Equal(t, "Application", first["tag"])
		assert.Equal(t, "Env", second["tag"])
	}
}

func TestGetItemsByIDs(t *testing.T) {
	var lastRequest ApiRequestPayload
	client := NewZabbixClientWithHandler(t, func(payload ApiRequestPayload) string {
		if payload.Method == "item.get" {
			lastRequest = payload
			return `{"result":[{"itemid":"1","name":"CPU"}]}`
		}

		if payload.Method ==  "apiinfo.version" {
			return `{"result":"6.4.0"}`
		}

		return `{"result":null}`
	})

	items, err := client.GetItemsByIDs(context.Background(), []string{"1"})
	require.NoError(t, err)
	assert.Len(t, items, 1)
	assert.Equal(t, []interface{}{"1"}, lastRequest.Params["itemids"].([]interface{}))
}

func TestGetAllApps(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"application.get": `{"result":[{"applicationid":"10","name":"API"}]}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	apps, err := client.GetAllApps(context.Background(), []string{"1"})
	require.NoError(t, err)
	assert.Len(t, apps, 1)
	assert.Equal(t, "API", apps[0].Name)
}

func TestGetAllHosts(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"host.get": `{"result":[{"hostid":"10","name":"web01"}]}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	hosts, err := client.GetAllHosts(context.Background(), []string{"1"})
	require.NoError(t, err)
	assert.Len(t, hosts, 1)
	assert.Equal(t, "web01", hosts[0].Name)
}

func TestGetAllGroups(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"hostgroup.get": `{"result":[{"groupid":"1","name":"Servers"}]}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	groups, err := client.GetAllGroups(context.Background())
	require.NoError(t, err)
	assert.Len(t, groups, 1)
	assert.Equal(t, "Servers", groups[0].Name)
}

func TestGetValueMappings(t *testing.T) {
	client := NewZabbixClientWithResponses(t, map[string]string{
		"valuemap.get": `{
			"result":[
				{
					"valuemapid":"1",
					"name":"Status",
					"mappings":[{"value":"0","newvalue":"down"}]
				}
			]
		}`,
		"apiinfo.version": `{"result":"6.4.0"}`,
	})

	valueMaps, err := client.GetValueMappings(context.Background())
	require.NoError(t, err)
	assert.Len(t, valueMaps, 1)
	assert.Equal(t, "Status", valueMaps[0].Name)
}
